package com.example.petplanet.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
public class CACHEUtil {

    private final StringRedisTemplate stringRedisTemplate;


    public CACHEUtil(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }

    public String getValueStr(String key){
        String value=stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(value)) return value;
        return null;
    }

    public void updateValueHash(String key, String hashKey,Object newValue, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForHash().put(key, hashKey, newValue);
        stringRedisTemplate.expire(key, time, unit);
    }

    public void expire(String key, Long time, TimeUnit unit){
        stringRedisTemplate.expire(key, time, unit);
    }

    public <R> void setHash(String key,R r,Long time,TimeUnit unit){
        //redis存入哈希值的转换
        //保存到redis中 这里存入的是哈希数据 在拦截器中取出的也是哈希数据
        Map<String, Object> userMap;
        if (r instanceof Map) {
            // 如果r是Map类型，直接强制类型转换
            userMap = (Map<String, Object>) r;
        } else {
            userMap= BeanUtil.beanToMap(r,new HashMap<>(),
                    CopyOptions.create().
                            setIgnoreNullValue(true).
                            setFieldValueEditor(
                                    (fieldName, fieldValue)->{
                                        if (fieldValue==null) return null;
                                        return fieldValue.toString();
                                    }
                            ));
        }
        stringRedisTemplate.opsForHash().putAll(key,userMap);
        stringRedisTemplate.expire(key,time,unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){

        RedisData redisData= new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));

        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public boolean delete(String key){
        return Boolean.TRUE.equals(stringRedisTemplate.delete(key));
    }

    public <R> R queryById(String key,Class<R> type){
        String json=stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) return JSONUtil.toBean(json,type);
        return null;
    }

    public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Long time, TimeUnit timeUnit, Function<ID,R> dbFallback)//函数式参数 ID是参数 R是返回值 都是类型
    {
        String key=keyPrefix+id;

        String json=stringRedisTemplate.opsForValue().get(key);

        if (StringUtils.isNotBlank(json)) return JSONUtil.toBean(json,type);

        if (json!=null) return null;

        R r=dbFallback.apply(id);

        if (r==null){
            stringRedisTemplate.opsForValue().set(key,"",2L, TimeUnit.MINUTES);
            return null;
        }

        this.set(key,r,time,timeUnit);
        return r;
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);

    public <R,ID> R queryWithLogicalExpire(String keyPrefix, ID id,Class<R> type, Long time, TimeUnit timeUnit, Function<ID,R> dbFallback){
        String key="shop:"+id;

        String json=stringRedisTemplate.opsForValue().get(key);

        if (StringUtils.isBlank(json))
        {
            return null;
        }


        RedisData redisData=JSONUtil.toBean(json, RedisData.class);
        R r=JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime=redisData.getExpireTime();

        if (expireTime.isAfter(LocalDateTime.now())){
            return r;
        }

        String lockKey="lock:shop:"+id;
        boolean isLock=tryLock(lockKey);

        if (isLock) {
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    R r1=dbFallback.apply(id);
                    this.setWithLogicalExpire(key,r1,time,timeUnit);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    unLock(lockKey);
                }
            });
        };
        return r;
    }

    private boolean tryLock(String key){
        Boolean flag=stringRedisTemplate.opsForValue().setIfAbsent(key,"1",10,TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }


    @Data
    public static class RedisData{
        private LocalDateTime expireTime;
        private Object data;
    }

}
